using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using ConfOrm.Mappers;
using NHibernate.Cfg.MappingSchema;
using NHibernate.UserTypes;

namespace ConfOrm.NH
{
	public class IdBagMapper : IIdBagPropertiesMapper
	{
		private readonly KeyMapper keyMapper;
		private readonly HbmIdbag idBagMapping;
		private readonly IAccessorPropertyMapper entityPropertyMapper;
		private ICacheMapper cacheMapper;
		private readonly ISimpleIdMapper idMapper;

		public IdBagMapper(Type ownerType, Type elementType, HbmIdbag idBagMapping)
		{
			if (ownerType == null)
			{
				throw new ArgumentNullException("ownerType");
			}
			if (elementType == null)
			{
				throw new ArgumentNullException("elementType");
			}
			if (idBagMapping == null)
			{
				throw new ArgumentNullException("idBagMapping");
			}
			OwnerType = ownerType;
			ElementType = elementType;
			this.idBagMapping = idBagMapping;
			if (idBagMapping.Key == null)
			{
				idBagMapping.key = new HbmKey();
			}
			keyMapper = new KeyMapper(ownerType, idBagMapping.Key);
			if (idBagMapping.collectionid == null)
			{
				idBagMapping.collectionid = new HbmCollectionId();
			}
			idMapper = new SimpleIdMapper(idBagMapping.collectionid);
			entityPropertyMapper = new AccessorPropertyMapper(ownerType, idBagMapping.Name, x => idBagMapping.access = x);
		}

		public Type OwnerType { get; private set; }
		public Type ElementType { get; private set; }

		#region Implementation of ICollectionPropertiesMapper

		public void Inverse(bool value)
		{
			idBagMapping.inverse = value;
		}

		public void Mutable(bool value)
		{
			idBagMapping.mutable = value;
		}

		public void Where(string sqlWhereClause)
		{
			idBagMapping.where = sqlWhereClause;
		}

		public void BatchSize(int value)
		{
			if (value > 0)
			{
				idBagMapping.batchsize = value;
				idBagMapping.batchsizeSpecified = true;
			}
			else
			{
				idBagMapping.batchsize = 0;
				idBagMapping.batchsizeSpecified = false;				
			}
		}

		public void Lazy(CollectionLazy collectionLazy)
		{
			idBagMapping.lazySpecified = true;
			switch (collectionLazy)
			{
				case CollectionLazy.Lazy:
					idBagMapping.lazy = HbmCollectionLazy.True;
					break;
				case CollectionLazy.NoLazy:
					idBagMapping.lazy = HbmCollectionLazy.False;
					break;
				case CollectionLazy.Extra:
					idBagMapping.lazy = HbmCollectionLazy.Extra;
					break;
			}
		}

		public void Key(Action<IKeyMapper> keyMapping)
		{
			keyMapping(keyMapper);
		}

		public void OrderBy(MemberInfo property)
		{
			// TODO: read the mapping of the element to know the column of the property (second-pass)
			idBagMapping.orderby = property.Name;
		}

		public void Sort() {}
		public void Sort<TComparer>() {}

		public void Cascade(Cascade cascadeStyle)
		{
			idBagMapping.cascade = cascadeStyle.ToCascadeString();
		}

		public void Type<TCollection>() where TCollection : IUserCollectionType
		{
			idBagMapping.collectiontype = typeof (TCollection).AssemblyQualifiedName;
		}

		public void Type(Type collectionType)
		{
			if (collectionType == null)
			{
				throw new ArgumentNullException("collectionType");
			}
			if (!typeof (IUserCollectionType).IsAssignableFrom(collectionType))
			{
				throw new ArgumentOutOfRangeException("collectionType",
													  string.Format(
																	"The collection type should be an implementation of IUserCollectionType.({0})",
																	collectionType));
			}
			idBagMapping.collectiontype = collectionType.AssemblyQualifiedName;
		}

		public void Table(string tableName)
		{
			idBagMapping.table = tableName;
		}

		public void Catalog(string catalogName)
		{
			idBagMapping.catalog = catalogName;
		}

		public void Schema(string schemaName)
		{
			idBagMapping.schema = schemaName;
		}

		public void Cache(Action<ICacheMapper> cacheMapping)
		{
			if (cacheMapper == null)
			{
				var hbmCache = new HbmCache();
				idBagMapping.cache = hbmCache;
				cacheMapper = new CacheMapper(hbmCache);
			}
			cacheMapping(cacheMapper);
		}

		public void Filter(string filterName, Action<IFilterMapper> filterMapping)
		{
			if (filterMapping == null)
			{
				filterMapping = x => { };
			}
			var hbmFilter = new HbmFilter();
			var filterMapper = new FilterMapper(filterName, hbmFilter);
			filterMapping(filterMapper);
			var filters = idBagMapping.filter != null ? idBagMapping.filter.ToDictionary(f => f.name, f => f) : new Dictionary<string, HbmFilter>(1);
			filters[filterName] = hbmFilter;
			idBagMapping.filter = filters.Values.ToArray();
		}

		public void Fetch(CollectionFetchMode fetchMode)
		{
			if (fetchMode == null)
			{
				return;
			}
			idBagMapping.fetch = fetchMode.ToHbm();
			idBagMapping.fetchSpecified = idBagMapping.fetch != HbmCollectionFetchMode.Select;
		}

		public void Id(Action<ISimpleIdMapper> collectionIdMapper)
		{
			collectionIdMapper(idMapper);
		}

		#endregion

		#region Implementation of IEntityPropertyMapper

		public void Access(Accessor accessor)
		{
			entityPropertyMapper.Access(accessor);
		}

		public void Access(Type accessorType)
		{
			entityPropertyMapper.Access(accessorType);
		}

		public void OptimisticLock(bool takeInConsiderationForOptimisticLock)
		{
			idBagMapping.optimisticlock = takeInConsiderationForOptimisticLock;
		}

		#endregion
	}
}